package com.gitee.ice1938.swagger2staticdocs;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver;
import org.springframework.boot.web.server.AbstractConfigurableWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

/**
 * 
 * @author sunyan 2020-03-28
 */
@SpringBootApplication
public class Application {
	private static final Logger log = LoggerFactory.getLogger(Application.class);

	public static void main(String[] args) {
		SpringApplication app = new SpringApplication(Application.class);
		ConfigurableApplicationContext appContext = app.run(args);
		logApplicationStartup(appContext);
	}

	private static void logApplicationStartup(ConfigurableApplicationContext appContext) {
		Environment env = appContext.getEnvironment();
		String protocol = "http";
		if (env.getProperty("server.ssl.key-store") != null) {
			protocol = "https";
		}
		String serverPort = env.getProperty("server.port");
		if (serverPort == null) {
			AbstractConfigurableWebServerFactory swf = appContext.getBean(AbstractConfigurableWebServerFactory.class);
			serverPort = String.valueOf(swf.getPort());
		}
		String contextPath = env.getProperty("server.servlet.context-path");
		if (!StringUtils.hasText(contextPath)) {
			contextPath = "/";
		}
		String hostAddress = "localhost";
		try {
			hostAddress = InetAddress.getLocalHost().getHostAddress();
		} catch (UnknownHostException e) {
			log.warn("The host name could not be determined, using `localhost` as fallback");
		}
		log.info("\n----------------------------------------------------------\n\t" + "Application '{}' is running! Access URLs:\n\t" + "Local: \t\t{}://localhost:{}{}\n\t"
				+ "External: \t{}://{}:{}{}\n\t" + "Profile(s): \t{}\n----------------------------------------------------------", env.getProperty("spring.application.name"), protocol, serverPort,
				contextPath, protocol, hostAddress, serverPort, contextPath, env.getActiveProfiles());
	}

	/**
	 * 由于ErrorMvcAutoConfiguration 中使用DefaultErrorViewResolver 类型进行识别，所有必须返回类型为
	 * DefaultErrorViewResolver
	 * 
	 * @param applicationContext
	 *            ApplicationContext
	 * @param resourceProperties
	 *            ResourceProperties
	 * @return DefaultErrorViewResolver
	 */
	@Bean
	@ConditionalOnMissingBean
	public DefaultErrorViewResolver defaultErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
		return new DefaultErrorViewResolver(applicationContext, resourceProperties) {
			@Override
			public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
				if (status.equals(HttpStatus.NOT_FOUND)) {

					return new ModelAndView(new HtmlResourceView(request, model), model);
				}

				return super.resolveErrorView(request, status, model);
			}
		};
	}

	/**
	 * {@link View} backed by an HTML resource.
	 */
	private static class HtmlResourceView implements View {

		private StringBuffer body = new StringBuffer();

		HtmlResourceView(HttpServletRequest request, Map<String, Object> model) {
			ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
			RequestMappingHandlerMapping requestMappingHandlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
			Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
			body.append("<!DOCTYPE html>");
			body.append("<html><head><meta charset=\"utf-8\">");

			body.append("<title>Simple Index</title>");
			body.append("</head>");
			body.append("<body>");
			try {
				Object bean = applicationContext.getBean("swagger2ControllerMapping");
				body.append("<a target =\"_blank\" href=\"");
				body.append(request.getContextPath());
				body.append("/swagger-ui.html\">swagger-ui.html</a><hr>");
			} catch (NoSuchBeanDefinitionException e) {
			}
			for (Entry<RequestMappingInfo, HandlerMethod> item : handlerMethods.entrySet()) {
				HandlerMethod value = item.getValue();
				if (value.getBean().equals("basicErrorController")) {
					continue;
				}
				RequestMappingInfo key = item.getKey();
				RequestMethodsRequestCondition method = key.getMethodsCondition();
				PatternsRequestCondition prc = key.getPatternsCondition();
				Set<String> patterns = prc.getPatterns();
				if (patterns.size() > 1) {
					// logger.warn(" MethodAnnotation have more patterns :{}",
					// prc.toString());
					// continue;
				}
				ResponseBody responseBody = value.getMethodAnnotation(ResponseBody.class);
				for (String reqUri : patterns) {
					if (reqUri.endsWith("/")) {
						reqUri = reqUri.substring(0, reqUri.length() - 1);
					}
					// 不是页面跳转方法
					if (responseBody == null) {
						if (!method.isEmpty() && !method.getMethods().contains(RequestMethod.GET)) {
							// logger.info("reqUri must be GET, {} method:{}",
							// reqUri, method);
							continue;
						}
						// 约定菜单不能包含path变量
						if (reqUri.contains("{")) {
							// logger.info("reqUri can't has pathvalue
							// :{},method:{}",
							// reqUri, method);
							continue;
						}
						// 跳转页面的方法<a href="http://www.123.com">访问!</a>
						body.append("<a target =\"_blank\" href=\"");
						body.append(request.getContextPath());
						body.append(reqUri);
						body.append("\">");
						body.append(value.getBean() + ":" + reqUri);
						body.append("</a><hr>");

					}
				}
			}
			body.append("</body></html>");
		}

		@Override
		public String getContentType() {
			return MediaType.TEXT_HTML_VALUE;
		}

		@Override
		public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
			response.setContentType(getContentType());
			InputStream input = new ByteArrayInputStream(body.toString().getBytes());
			FileCopyUtils.copy(input, response.getOutputStream());
		}

	}

}
